(function() {
	// shortcut to regEnumClass
	var regEnumClass = jMass.regEnumClass,
	// shortcut to regEnumMethod
	regEnumMethod = jMass.regEnumMethod,

	/* Enumerators */
	whereEnum = fluentQryBaseEnum.extend( {
		name : "where",
		prepare : function(clause) {
			this.$where = baseEnum_getClause(clause);
		},
		next : function() {
			while (this.source.next()) {
				if (this.$where.call(this, this.source.current)) {
					this.current = this.source.current;
					return true;
				}
			}
			this.current = null;
			return false;
		}
	}),

	selectEnum = fluentQryBaseEnum
			.extend( {
				name : "select",
				prepare : function(func) {
					this.$select = baseEnum_getClause(func,
							default_clauses_returnCurrent);
				},
				next : function() {
					var itemValue;
					while (this.source.next()) {
						if ((itemValue = this.$select.call(this,
								this.source.current)) !== null) {
							this.current = itemValue;
							return true;
						}
					}
					this.current = null;
					return false;
				}
			}),

	selectManyEnum = fluentQryBaseEnum.extend( {
		name : "selectMany",
		prepare : function(clause) {
			this.$select = baseEnum_getClause(clause,
					default_clauses_returnCurrent);
		},
		reset : function() {
			this.base();
			this.$buffer = null;
		},
		next : function() {
			var buffer = this.$buffer;
			if (buffer) {
				if (buffer.isEnum && buffer.next()) {
					this.current = buffer.current;
					return true;
				} else if (jMass.isArray(buffer) && buffer.length
						|| buffer.isList && buffer.length()) {
					this.current = buffer.shift();
					return true;
				}
			}
			var itemValue = null;
			var newBuffer = null;
			while (this.source.next()) {
				itemValue = this.$select(this.source.current);
				if (itemValue !== null) {
					newBuffer = jMass.isArray(itemValue) && itemValue.length
							|| itemValue.isEnum ? itemValue
							: itemValue.toEnum ? itemValue.toEnum() : null;
					if (newBuffer) {
						this.$buffer = newBuffer;
						return this.next();
					}
					this.$buffer = null;
					this.current = itemValue;
					return true;
				}
			}
			this.current = null;
			return false;
		}
	}),

	takeEnum = fluentQryBaseEnum.extend( {
		name : "take",
		prepare : function(count) {
			this.$count = count;
		},
		reset : function() {
			this.base();
			this.$taked = 0;
		},
		next : function() {
			if (this.$taked < this.$count) {
				while (this.source.next()) {
					this.current = this.source.current;
					++this.$taked;
					return true;
				}
			}
			this.current = null;
			return false;
		}
	}),

	takeWhileEnum = fluentQryBaseEnum.extend( {
		name : "takeWhile",
		prepare : function(clause) {
			this.$clause = baseEnum_getClause(clause);
		},
		next : function() {
			while (this.source.next() && this.$clause(this.source.current)) {
				this.current = this.source.current;
				return true;
			}
			this.current = null;
			return false;
		}
	}),

	exceptEnum = fluentQryBaseEnum
			.extend( {
				name : "except",
				prepare : function(second, equalityComparer) {
					this.$second = jMass.getEnum(second);
					this.$comparer = baseEnum_getClause(equalityComparer,
							default_clauses_equalityComparer);
				},
				next : function() {
					var second = this.$second;
					while (this.source.next()) {
						second.reset();
						while (second.next()
								&& !this.$comparer(this.source.current,
										second.current)) {
						}
						if (second.current === null) {
							this.current = this.source.current;
							return true;
						}
					}
					this.current = null;
					return false;
				}
			}),

	unionEnum = fluentQryBaseEnum.extend( {
		name : "union",
		prepare : function(second, equalityComparer) {
			this.$second = jMass.getEnum(second);
			this.$comparer = baseEnum_getClause(equalityComparer,
					default_clauses_equalityComparer);
		},
		reset : function() {
			this.base();
			this.$cache = [];
			this.$cacheSize = null;
		},
		next : function() {
			if (this.source.next()) {
				this.current = this.source.current;
				this.$cache.push(this.current);
				return true;
			} else {
				if (this.$cacheSize === null) {
					this.$second.reset();
					this.$cacheSize = this.$cache.length;
				}
				var isEq;
				while (this.$second.next()) {
					isEq = false;
					for ( var i = 0; i < this.$cacheSize && !isEq; i++) {
						isEq = this.$comparer(this.$second.current,
								this.$cache[i]);
					}
					if (!isEq) {
						this.current = this.$second.current;
						return true;
					}
				}
			}
			this.current = null;
			return false;
		}
	}),

	concatEnum = fluentQryBaseEnum.extend( {
		name : "concat",
		prepare : function(second) {
			this.$second = jMass.getEnum(second);
		},
		reset : function() {
			this.base();
			this.$onFirst = true;
		},
		next : function() {
			if (this.$onFirst && this.source.next()) {
				this.current = this.source.current;
				return true;
			} else {
				if (this.$onFirst) {
					this.$second.reset();
					this.$onFirst = false;
				}
				if (this.$second.next()) {
					this.current = this.$second.current;
					return true;
				}
			}
			this.current = null;
			return false;
		}
	}),

	distinctEnum = fluentQryBaseEnum.extend( {
		name : "distinct",
		prepare : function(equalityComparer) {
			this.$comparer = baseEnum_getClause(equalityComparer,
					default_clauses_equalityComparer);
		},
		reset : function() {
			this.base();
			this.$cache = [];
		},
		next : function() {
			var isEq = false;
			while (this.source.next()) {
				isEq = false;
				for ( var i = 0, cacheLen = this.$cache.length; i < cacheLen
						&& !isEq; i++)
					isEq = this.$comparer(this.source.current, this.$cache[i]);
				this.$cache.push(this.source.current);
				if (!isEq) {
					this.current = this.source.current;
					return true;
				}
			}
			this.current = null;
			return false;
		}
	}),

	skipEnum = fluentQryBaseEnum.extend( {
		name : "skip",
		prepare : function(count) {
			this.$count = count;
		},
		next : function() {
			if (this.$count > 0) {
				while (this.source.next() && --this.$count > 0) {
				}
			}
			if (this.source.next()) {
				this.current = this.source.current;
				return true;
			}
			this.current = null;
			return false;
		}
	}),

	skipWhileEnum = fluentQryBaseEnum
			.extend( {
				name : "skipWhile",
				prepare : function(clause) {
					this.$clause = baseEnum_getClause(clause,
							default_clauses_returnCurrent);
				},
				reset : function() {
					this.base();
					this.$skipped = false;
				},
				next : function() {
					if (!this.$skipped) {
						while (this.source.next()
								&& this.$clause(this.source.current)) {
						}
						this.$skipped = true;
					}
					if (this.source.next()) {
						this.current = this.source.current;
						return true;
					}
					this.current = null;
					return false;
				}
			}),

	groupByEnum = fluentQryBaseEnum.extend( {
		name : "groupBy",
		prepare : function(clause) {
			this.$clause = baseEnum_getClause(clause,
					default_clauses_returnCurrent);
		},
		reset : function() {
			this.base();
			this.$pending = true;
			this.$groups = [];
		},
		next : function() {
			if (this.$pending) {
				this.$pending = false;
				var groups = {};
				var key;
				while (this.source.next()) {
					key = this.$clause(this.source.current);
					if (key !== null) {
						if (groups[key] !== undefined)
							this.$groups[groups[key]].items
									.push(this.source.current);
						else
							groups[key] = this.$groups.push( {
								key : key,
								items : [ this.source.current ]
							}) - 1;
					}
				}
			}
			if (this.$groups.length) {
				this.current = this.$groups.shift();
				return true;
			}
			this.current = null;
			return false;
		}
	}),

	joinEnum = fluentQryBaseEnum
			.extend( {
				name : 'join',
				prepare : function(second, matchClause, resultClause) {
					this.$second = jMass.getEnum(second);
					this.$match = baseEnum_getClause(matchClause,
							default_clauses_equalityComparer);
					this.$result = baseEnum_getClause(resultClause, function(x,
							y) {
						return [ x, y ];
					});
				},
				reset : function() {
					this.base();
					this.$lastMatch = false;
				},
				next : function() {
					while (true) {
						if (!this.$lastMatch) {
							if (!this.source.next()) {
								this.current = null;
								return false;
							}
							this.$second.reset();
						}
						while (this.$second.next()) {
							if (this.$match(this.source.current,
									this.$second.current)) {
								this.current = this.$result(
										this.source.current,
										this.$second.current);
								this.$lastMatch = true;
								return true;
							}
						}
						this.$lastMatch = false;
					}
				}
			}),

	leftJoinEnum = fluentQryBaseEnum
			.extend( {
				name : 'leftjoin',
				prepare : function(second, matchClause, resultClause) {
					this.$second = jMass.getEnum(second);
					this.$match = baseEnum_getClause(matchClause,
							default_clauses_equalityComparer);
					this.$result = baseEnum_getClause(resultClause, function(x,
							y) {
						return [ x, y ];
					});
				},
				reset : function() {
					this.base();
					this.$lastMatch = false;
				},
				next : function() {
					while (true) {
						if (!this.$lastMatch) {
							if (!this.source.next()) {
								this.current = null;
								return false;
							}
							this.$second.reset();
						}
						while (this.$second.next()) {
							if (this.$match(this.source.current,
									this.$second.current)) {
								this.current = this.$result(
										this.source.current,
										this.$second.current);
								this.$lastMatch = true;
								return true;
							}
						}
						if (!this.$lastMatch) {
							this.current = this.$result(this.source.current,
									undefined);
							return true;
						}
						this.$lastMatch = false;
					}
				}
			});

	regEnumClass("Where", whereEnum);
	regEnumMethod("where", function(clause) {
		return new whereEnum(this, clause);
	});

	regEnumClass("Select", selectEnum);
	regEnumMethod("select", function(func) {
		return new selectEnum(this, func);
	});

	regEnumClass("SelectMany", selectManyEnum);
	regEnumMethod("selectMany", function(func) {
		return new selectManyEnum(this, func);
	});

	regEnumClass("Take", takeEnum);
	regEnumMethod("take", function(count) {
		return new takeEnum(this, count);
	});

	regEnumClass("TakeWhile", takeWhileEnum);
	regEnumMethod("takeWhile", function(clause) {
		return new takeWhileEnum(this, clause);
	});

	regEnumClass("Except", exceptEnum);
	regEnumMethod("except", function(second, equalityComparer) {
		return new exceptEnum(this, second, equalityComparer);
	});

	regEnumClass("Union", unionEnum);
	regEnumMethod("union", function(second, equalityComparer) {
		return new unionEnum(this, second, equalityComparer);
	});

	regEnumClass("Concat", concatEnum);
	regEnumMethod("concat", function(second) {
		return new concatEnum(this, second);
	});

	regEnumClass("Distinct", distinctEnum);
	regEnumMethod("distinct", function(equalityComparer) {
		return new distinctEnum(this, equalityComparer);
	});

	regEnumClass("Skip", skipEnum);
	regEnumMethod("skip", function(count) {
		return new skipEnum(this, count);
	});

	regEnumClass("SkipWhile", skipWhileEnum);
	regEnumMethod("skipWhile", function(clause) {
		return new skipWhileEnum(this, clause);
	});

	regEnumClass("GroupBy", groupByEnum);
	regEnumMethod("groupBy", function(keyFunc) {
		return new groupByEnum(this, keyFunc);
	});

	regEnumClass("Join", joinEnum);
	regEnumMethod("join", function(second, matchClause, resultClause) {
		return new joinEnum(this, second, matchClause, resultClause);
	});

	regEnumClass("LeftJoin", leftJoinEnum);
	regEnumMethod("leftJoin", function(second, matchClause, resultClause) {
		return new leftJoinEnum(this, second, matchClause, resultClause);
	});
})();
